#!/usr/bin/env python


"""
This script uses a regex to validate the output of our backtraces.
The layout we assume is:

<frame number> <library name> <address> <demangled name> + <offset>

It currently just checks that the backtrace results in at least one correctly
formatted entry. It does not directly validate the input since the output
would not be robust against standard library changes.

TODO: We could have the user pass in the frame number, library name, and
demangled name. These we can always validate as true in a robust way. On the
other hand, address and offset are more difficult to validate in a robust way
in the face of small codegen differences, so without any further thought I
imagine we can just check the format.

11 libswiftCore.dylib 0x000000000dce84d0l _fatalErrorMessage(StaticString,
StaticString, StaticString, UInt, flags : UInt32) -> () + 444
"""


from __future__ import absolute_import, print_function, unicode_literals

import argparse
import re
import sys


# -----------------------------------------------------------------------------
# Constants

DESCRIPTION = """
Checks that a stacktrace dump follows canonical formatting.
"""

TARGET_PATTERN = re.compile(
    r'(?P<index>\d+) +(?P<object>\S+) +(?P<address>0x[0-9a-fA-F]{16}) '
    r'(?P<routine>[^+]+) [+] (?P<offset>\d+)')

RESULTS_FORMAT = """Stack Trace Entry:
\tIndex: '%(index)s'
\tObject File: '%(object)s'
\tAddress: '%(address)s'
\tRoutine: '%(routine)s'
\tOffset: '%(offset)s'
"""


# -----------------------------------------------------------------------------

def parse_args():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description=DESCRIPTION)

    parser.add_argument(
        '-u', '--check-unavailable',
        action='store_true',
        help='Checks if any symbols were unavailable')

    return parser.parse_args()


def main():
    args = parse_args()

    lines = sys.stdin.readlines()

    found_stack_trace_start = False
    found_stack_trace_entry = False
    for line in lines:
        line = line.rstrip('\n')

        # First see if we found the start of our stack trace start. If so, set
        # the found stack trace flag and continue.
        if line == 'Current stack trace:':
            assert(not found_stack_trace_start)
            found_stack_trace_start = True
            continue

        # Otherwise, if we have not yet found the stack trace start, continue.
        # We need to find the stack trace start line.
        if not found_stack_trace_start:
            continue

        # Ok, we are in the middle of matching a stack trace entry.
        matches = TARGET_PATTERN.match(line)

        # If we fail to match, we have exited the stack trace entry region
        if matches is None:
            break

        # At this point, we know that we have some sort of match.
        found_stack_trace_entry = True
        print(RESULTS_FORMAT.format(**matches.groupdict()))

        # Check for unavailable symbols, if that was requested.
        if args.check_unavailable:
            assert('unavailable' not in matches.group('routine'))

    # Once we have processed all of the lines, make sure that we found at least
    # one stack trace entry.
    assert(found_stack_trace_entry)


if __name__ == '__main__':
    main()
